/**
 *  \file monAirportRhapsodyPa.c (implementation file)
 *
 *  \brief Problem name: Airport rhapsody.
 *
 *  \brief Concept: António Rui Borges
 *
 *  Synchronization based on monitors.
 *  Both threads and the monitor are implemented using the pthread library which enables the creation of a
 *  monitor of the Lampson / Redell type.
 *
 *  Definition of the operations carried out by the passenger threads:
 *     \li what should I do
 *     \li go collect a bag
 *     \li report missing bags
 *     \li go home
 *     \li take a bus
 *     \li enter the bus
 *     \li leave the bus
 *     \li prepare next leg.
 *
 *  \author António Rui Borges - December 2013
 */

#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <pthread.h>
#include <errno.h>

#include "probConst.h"
#include "probDataStruct.h"
#include "cam.h"
#include "queue.h"
#include "logging.h"
#include "monAirportRhapsodyIDS.h"

/** \brief logging file name */
extern char nFic[51];

/** \brief passenger threads return status */
extern int statusPA[N];

/** \brief the passenger has this airport as her final destination and has bags to collect */
#define FDBTC          0

/** \brief the passenger has this airport as her final destination with no bags to collect */
#define FDNBTC         1

/** \brief the passenger is in transit */
#define INTRAN         2

/**
 *  \brief What should I do.
 *
 *  The passenger descending from the plane decides what should she do according to her situation (has this airport
 *  as her final destination and has bags to collect / has this airport as her final destination with no bags to
 *  collect / is in transit) and updates statistical information.
 *
 *  If she is the very last passenger descending from the plane, she must inform the porter that a new plane has
 *  landed. If she is the very first passenger, she sets the number of the plane landing.
 *
 *  State should only be saved by the very first passenger descending from the plane.
 *
 *  \param k plane landing number
 *  \param id passenger identification
 *
 *  \return \c FDBTC, if she has this airport as her final destination and has bags to collect
 *  \return \c FDNBTC, if she has this airport as her final destination with no bags to collect
 *  \return \c INTRAN, if she is in transit
 */

unsigned int whatShouldIDo (unsigned int k, unsigned int id)
{
  if ((statusPA[id] = pthread_mutex_lock (&accessCR)) != 0)                                         /* enter monitor */
  { errno = statusPA[id];                                                                  /* save error in errno */
    perror ("error on entering the monitor (PA)");
    statusPA[id] = EXIT_FAILURE;
    pthread_exit (&statusPA[id]);
  }
  pthread_once (&init, initialization);                                              /* internal data initialization */
  
  /* insert youy code here */
  unsigned int retorno;
  
  
  if (nPassP%N == 0)
  {
    fSt.nLand=k;
    fSt.st.passStat[k][id].stat = AT_THE_DISEMBARKING_ZONE;
    saveState(nFic, k, &fSt);
  }
  
  
  if (fSt.st.passStat[k][id].sit == FD)
  {
    fSt.nToTPassFD++;
    if (fSt.st.passStat[k][id].nBagsReal > 0)
    {
      retorno= FDBTC;
    }
    else
    {
      retorno = FDNBTC;
    }
  }
  else
  {
    fSt.nToTPassTST++;
    retorno =INTRAN;  
  }
  
  nPassP++; 
  
  if (nPassP%N == 0)
  {
    pthread_cond_signal(&waitingFlight); 
  }
  
  
  if ((statusPA[id] = pthread_mutex_unlock (&accessCR)) != 0)                                        /* exit monitor */
  {
    errno = statusPA[id];                                                                  /* save error in errno */
    perror ("error on exiting monitor (PA)");
    statusPA[id] = EXIT_FAILURE;
    pthread_exit (&statusPA[id]);
  }
  
  
  return retorno;
  
}

/** \brief the passenger has collected all her bags */
#define YES            0

/** \brief the passenger has not yet collected all her bags */
#define NO             1

/** \brief the passenger has missing bags */
#define MB             3

/**
 *  \brief Go collect a bag.
 *
 *  The passenger waits until she receives a call from the porter. She then checks if there is a bag on the belt
 *  conveyor that belongs to her. If so, she picks it up and updates the number of bags already recovered. If not,
 *  she updates statistical information. Upon exit, a decision is taken about her present situation.
 *
 *  State may be be saved twice. The first of the two only if there is a change of state of the passenger.
 *
 *  \param k plane landing number
 *  \param id passenger identification
 *
 *  \return \c YES, if she has collected all her bags
 *  \return \c NO, if she has not yet collected all her bags
 *  \return \c MB, if she has missing bags
 */

unsigned int goCollectABag (unsigned int k, unsigned int id)
{
  if ((statusPA[id] = pthread_mutex_lock (&accessCR)) != 0)                                         /* enter monitor */
  { errno = statusPA[id];                                                                  /* save error in errno */
    perror ("error on entering the monitor (PA)");
    statusPA[id] = EXIT_FAILURE;
    pthread_exit (&statusPA[id]);
  }
  pthread_once (&init, initialization);                                              /* internal data initialization */
  
  /* insert youy code here */
  
  unsigned int retorno;
  
  if (fSt.st.passStat[k][id].stat == AT_THE_DISEMBARKING_ZONE)
  {
    fSt.st.passStat[k][id].stat = AT_THE_LUGGAGE_COLLECTION_POINT;
    saveState(nFic, k,&fSt); 
  }
  
  while(nCalls[id]==0)
    pthread_cond_wait(&pass[id],&accessCR); 
  
  nCalls[id]--; 
  
  if (camSearch(&(fSt.convBelt),id))
  {
    camOut (&(fSt.convBelt),id); 
    fSt.nToTBagsPCB--;
    fSt.st.passStat[k][id].nBagsAct++;
    saveState(nFic, k, &fSt);
    
    if (fSt.st.passStat[k][id].nBagsReal != fSt.st.passStat[k][id].nBagsAct)
      retorno = NO;
    else
      retorno=YES; 
  }
  else
  {
    retorno= MB;
    fSt.nToTMBags += (fSt.st.passStat[k][id].nBagsReal - fSt.st.passStat[k][id].nBagsAct); 
  }
  
  
  if ((statusPA[id] = pthread_mutex_unlock (&accessCR)) != 0)                                        /* exit monitor */
  { errno = statusPA[id];                                                                  /* save error in errno */
    perror ("error on exiting monitor (PA)");
    statusPA[id] = EXIT_FAILURE;
    pthread_exit (&statusPA[id]);
  }
  
  return retorno;
}

/**
 *  \brief Report missing bags.
 *
 *  The passenger fills the form for missing bags.
 *
 *  State should be saved.
 *
 *  \param k plane landing number
 *  \param id passenger identification
 */

void reportMissingBags (unsigned int k, unsigned int id)
{
  if ((statusPA[id] = pthread_mutex_lock (&accessCR)) != 0)                                         /* enter monitor */
  { errno = statusPA[id];                                                                  /* save error in errno */
    perror ("error on entering the monitor (PA)");
    statusPA[id] = EXIT_FAILURE;
    pthread_exit (&statusPA[id]);
  }
  pthread_once (&init, initialization);                                              /* internal data initialization */
  
  fSt.st.passStat[k][id].stat = AT_THE_BAGGAGE_RECLAIM_OFFICE;
  saveState(nFic, k, &fSt); 
  
  if ((statusPA[id] = pthread_mutex_unlock (&accessCR)) != 0)                                        /* exit monitor */
  { errno = statusPA[id];                                                                  /* save error in errno */
    perror ("error on exiting monitor (PA)");
    statusPA[id] = EXIT_FAILURE;
    pthread_exit (&statusPA[id]);
  }
}

/**
 *  \brief Go home.
 *
 *  The passenger exits the airport.
 *
 *  However, before actually doing that, she waits for all other passengers being ready to either exit the airport or
 *  also enter the departure terminal and, if she is the very last passenger of the very last flight, she informs the
 *  bus driver that his day's work is finished.
 *
 *  State should be saved.
 *
 *  \param k plane landing number
 *  \param id passenger identification
 */

void goHome (unsigned int k, unsigned int id)
{
  if ((statusPA[id] = pthread_mutex_lock (&accessCR)) != 0)                                         /* enter monitor */
  { errno = statusPA[id];                                                                  /* save error in errno */
    perror ("error on entering the monitor (PA)");
    statusPA[id] = EXIT_FAILURE;
    pthread_exit (&statusPA[id]);
  }
  pthread_once (&init, initialization);                                              /* internal data initialization */
  
  /* insert youy code here */
  int i;
  bool ultimo;
  
  fSt.st.passStat[k][id].stat = ENTERING_THE_DEPARTURE_TERMINAL;
  saveState(nFic,k,&fSt);
  
  
  do
  {
    ultimo=true;
    for(i=0;i<N;i++)
    {
      if ((fSt.st.passStat[k][i].stat != EXITING_THE_ARRIVAL_TERMINAL )&& (fSt.st.passStat[k][i].stat != ENTERING_THE_DEPARTURE_TERMINAL))
      {
	ultimo=false;
	break;
      }
    }
    
    if (ultimo==false)
      pthread_cond_wait(&pass[id],&accessCR); 
    
  }while(ultimo==false);
  
  if (ultimo && k == K-1)
    fSt.dayEnded=true;  //informar o motorista
    
    if (ultimo)
    {
      for(i=0;i<N;i++)
	if (i!=id)
	  pthread_cond_signal(&pass[i]); 
    }
    
    if ((statusPA[id] = pthread_mutex_unlock (&accessCR)) != 0)                                        /* exit monitor */
    { errno = statusPA[id];                                                                  /* save error in errno */
      perror ("error on exiting monitor (PA)");
      statusPA[id] = EXIT_FAILURE;
      pthread_exit (&statusPA[id]);
    }
}

/**
 *  \brief Take a bus.
 *
 *  The transit passenger queues at the arrival transfer terminal to take a bus to the departure transfer terminal.
 *
 *  However, before waiting her turn, she informs the bus driver it is time to start boarding if the number of queueing
 *  passengers is equal to the number of seats in the bus.
 *
 *  State should be saved.
 *
 *  \param k plane landing number
 *  \param id passenger identification
 */

void takeABus (unsigned int k, unsigned int id)
{
  if ((statusPA[id] = pthread_mutex_lock (&accessCR)) != 0)                                         /* enter monitor */
  { errno = statusPA[id];                                                                  /* save error in errno */
    perror ("error on entering the monitor (PA)");
    statusPA[id] = EXIT_FAILURE;
    pthread_exit (&statusPA[id]);
  }
  pthread_once (&init, initialization);                                              /* internal data initialization */
  
  /* insert youy code here */
  fSt.st.passStat[k][id].stat = AT_THE_ARRIVAL_TRANSFER_TERMINAL;
  
  queueIn(&(fSt.busQueue),id);
  
  if (queuePeek(&(fSt.busQueue),T-1) == id )
    pthread_cond_signal(&waitingDrive);
  
  saveState(nFic, k, &fSt); 
  
  while(nCalls[id]==0)
    pthread_cond_wait(&pass[id],&accessCR); 
  
  nCalls[id]--; 
  
  if ((statusPA[id] = pthread_mutex_unlock (&accessCR)) != 0)                                        /* exit monitor */
  { errno = statusPA[id];                                                                  /* save error in errno */
    perror ("error on exiting monitor (PA)");
    statusPA[id] = EXIT_FAILURE;
    pthread_exit (&statusPA[id]);
  }
}

/**
 *  \brief Enter the bus.
 *
 *  The passenger enters the bus that it is going to take her to the departure transfer terminal and occupies
 *  an available seat.
 *  If it is already packed full, she issues an error message.
 *  If she is the last one to board the bus for the ride, she informs the driver he may start the journey.
 *
 *  State should be saved.
 *
 *  \param k plane landing number
 *  \param id passenger identification
 */

void enterTheBus (unsigned int k, unsigned int id)
{
  if ((statusPA[id] = pthread_mutex_lock (&accessCR)) != 0)                                         /* enter monitor */
  { errno = statusPA[id];                                                                  /* save error in errno */
    perror ("error on entering the monitor (PA)");
    statusPA[id] = EXIT_FAILURE;
    pthread_exit (&statusPA[id]);
  }
  pthread_once (&init, initialization);                                              /* internal data initialization */
  
  /* insert youy code here */
  
  fSt.st.passStat[k][id].stat = TERMINAL_TRANSFER;
  
  if (fSt.bus.nOccup>=T)
  {
    fprintf(stderr, "Bus is already packed full \n");
    statusPA[id] = EXIT_FAILURE;
    pthread_exit (&statusPA[id]);
  }
  else
  {
    
    fSt.bus.seat[fSt.bus.nOccup]=id;
    fSt.bus.nOccup++; 
    nPassD--;
    
    if (nPassD==0) 
      pthread_cond_signal(&waitingPass);
  }
  
  saveState(nFic, k, &fSt); 
  
  while(nCalls[id]==0)
    pthread_cond_wait(&pass[id],&accessCR); 
  
  nCalls[id]--; 
  
  if ((statusPA[id] = pthread_mutex_unlock (&accessCR)) != 0)                                        /* exit monitor */
  { errno = statusPA[id];                                                                  /* save error in errno */
    perror ("error on exiting monitor (PA)");
    statusPA[id] = EXIT_FAILURE;
    pthread_exit (&statusPA[id]);
  }
}

/**
 *  \brief Leave the bus.
 *
 *  The passenger checks the bus occupation. If it is already empty, she issues an error message.
 *  The transit passenger leaves the bus that has taken her to the departure transfer terminal.
 *  If she is the last one to exit, she informs the driver he may start the journey back.
 *
 *  State should be saved.
 *
 *  \param k plane landing number
 *  \param id passenger identification
 */

void leaveTheBus (unsigned int k, unsigned int id)
{
  if ((statusPA[id] = pthread_mutex_lock (&accessCR)) != 0)                                         /* enter monitor */
  { errno = statusPA[id];                                                                  /* save error in errno */
    perror ("error on entering the monitor (PA)");
    statusPA[id] = EXIT_FAILURE;
    pthread_exit (&statusPA[id]);
  }
  pthread_once (&init, initialization);                                              /* internal data initialization */
  
  /* insert youy code here */
  
  if (fSt.bus.nOccup==0)
  {
    fprintf(stderr, "Bus is already empty \n");
    statusPA[id] = EXIT_FAILURE;
    pthread_exit (&statusPA[id]);
  }
  else
  {
    fSt.bus.seat[--fSt.bus.nOccup]=EMPTYPOS;
    
    nPassD--;  
    
    if (nPassD==0)
      pthread_cond_signal(&waitingPass); 
  }
  
  fSt.st.passStat[k][id].stat = AT_THE_DEPARTURE_TRANSFER_TERMINAL;
  saveState(nFic, k, &fSt); 
  
  if ((statusPA[id] = pthread_mutex_unlock (&accessCR)) != 0)                                        /* exit monitor */
  { errno = statusPA[id];                                                                  /* save error in errno */
    perror ("error on exiting monitor (PA)");
    statusPA[id] = EXIT_FAILURE;
    pthread_exit (&statusPA[id]);
  }
}

/**
 *  \brief Prepare next leg.
 *
 *  The passenger enters the departure terminal.
 *
 *  However, before actually doing that, she waits for all other passengers being ready to either exit the airport or
 *  also enter the departure terminal and, if she is the very last passenger of the very last flight, she informs the
 *  bus driver that his day's work is finished.
 *
 *  State should be saved.
 *
 *  \param k plane landing number
 *  \param id passenger identification
 */

void prepareNextLeg (unsigned int k, unsigned int id)
{
  if ((statusPA[id] = pthread_mutex_lock (&accessCR)) != 0)                                         /* enter monitor */
  { errno = statusPA[id];                                                                  /* save error in errno */
    perror ("error on entering the monitor (PA)");
    statusPA[id] = EXIT_FAILURE;
    pthread_exit (&statusPA[id]);
  }
  pthread_once (&init, initialization);                                              /* internal data initialization */
  
  /* insert youy code here */
  int i;
  bool ultimo;
  
  fSt.st.passStat[k][id].stat = ENTERING_THE_DEPARTURE_TERMINAL; 
  saveState(nFic, k, &fSt);
  
  do
  {
    ultimo=true;
    for(i=0;i<N;i++)
    {
      if ((fSt.st.passStat[k][i].stat != EXITING_THE_ARRIVAL_TERMINAL )&& (fSt.st.passStat[k][i].stat != ENTERING_THE_DEPARTURE_TERMINAL))
      {
	ultimo=false;
	break;
      }
    }
    
    if (ultimo==false)
      pthread_cond_wait(&pass[id],&accessCR); 
    
  }while(ultimo==false);
  
  if (ultimo && k == K-1)
    fSt.dayEnded=true;  //informar o motorista
    
    if (ultimo)
    {
      for(i=0;i<N;i++)
	if (i!=id)
	  pthread_cond_signal(&pass[i]); 
    }
    
    if ((statusPA[id] = pthread_mutex_unlock (&accessCR)) != 0)                                        /* exit monitor */
    { errno = statusPA[id];                                                                  /* save error in errno */
      perror ("error on exiting monitor (PA)");
      statusPA[id] = EXIT_FAILURE;
      pthread_exit (&statusPA[id]);
    }
}
